-
Notifications
You must be signed in to change notification settings - Fork 779
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(preload): cssom assets #958
Conversation
function start() { | ||
// Stop messing with my tests Mocha! | ||
document.querySelector('#mocha h1').outerHTML = | ||
'<h2>preload cssom integration test</h2>'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does that mean "otherwise there is no title generated"?
lib/core/utils/preload-cssom.js
Outdated
timeout | ||
}) | ||
.then(({ data }) => { | ||
const sheet = getSheetFromTextFn(data, true); //second argument acts as > isExternal - true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That second argument is useful. It's always external. More importantly, isExternal
shouldn't be set in getSheetFromTextFn
at all. It belongs in the same function that you set isExternal = false (this one).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Solved!
lib/core/utils/preload-cssom.js
Outdated
const q = axe.utils.queue(); | ||
|
||
Array.from(ownerDocument.styleSheets).forEach(sheet => { | ||
if (sheet.disabled) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should add && sheet.cssRules.length <= 0
, rather than add an if statement in the try/catch block. Currently the code looks like it's missing an "else".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We cannot move .cssRules to here, as we need the catch to trigger for external stylesheets. Trying to read a .cssRules on external resource throws a SecurityError, which flows into the catch block. This is documented below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although I have refactored slightly
lib/core/utils/preload-cssom.js
Outdated
* @return {Object} | ||
*/ | ||
function preloadCssom({ timeout, treeRoot = axe._tree[0] }) { | ||
const documents = getDocumentsFromTreeRoot(treeRoot); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we're not doing shadow DOM in this PR, than we need to replace this expression. Otherwise we're just committing buggy shadow DOM code to develop, rather than not having it supported.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have just solved for shadowDOM, and it would make sense to fold all of that into the same PR. So yes, shadowDOM is in the same PR.
lib/core/utils/preload.js
Outdated
*/ | ||
function isValidPreloadObject(preload) { | ||
if (typeof preload !== 'object') { | ||
return preload; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
preload=false shouldn't return false in isValidPreloadObject
. This is misleading.
return axe.utils.preloadCssom(config); | ||
} | ||
|
||
function commonTestsForRootAndFrame(root) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One more thing we forgot to test is if cross domain sheets loaded through @import can be accessed. I suspect they might not, so there's still some work in there I think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, you are right. I have come up with a solution for the same. Which is why shadowDOM work is going into this PR as against a new one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changes done. Also supports shadowDOM
lib/core/utils/preload.js
Outdated
|
||
// if type is object - ensure an array of assets to load is specified | ||
if (!isValidPreloadObject(options.preload)) { | ||
throw new Error( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code will never run because if it isn't, your shouldPreload
function causes utils.preload to exit early.
// await loading all such documents assets, and concat results into one object | ||
.then(assets => { | ||
resolve( | ||
assets.reduce((out, cssomSheets) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could have done return out.concat(cssomSheets)
.
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
if (typeof options.preload === 'boolean') { | ||
return options.preload; | ||
} | ||
return isValidPreloadObject(options.preload); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd suggest we run this first and throw if its invalid, rather than do it in getPreloadConfig
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is what we are doing.
const shouldPreload = axe.utils.shouldPreload(options);
if (!shouldPreload) {
return q;
}
const preloadConfig = axe.utils.getPreloadConfig(options);
function start() { | ||
// Stop messing with my tests Mocha! | ||
document.querySelector('#mocha h1').outerHTML = | ||
'<h2>preload cssom integration test</h2>'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It isn't. I put this in the page-has-heading-one tests to make sure I can test that page without mocha getting in the way. That's the only place its needed.
} | ||
); | ||
|
||
shouldIt('should reject external stylesheets', function(done) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean, "should reject if external sheet fails to load"?
lib/core/utils/preload-cssom.js
Outdated
|
||
// there are external rules, best to filter non-external rules and create a new stylesheet to avoid duplication | ||
const nonExternalRules = rules.filter(rule => !rule.href); | ||
// concat all cssText into a string for non external rules |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm having a very hard time understanding what's happening here. As best I can tell you're trying to turn same origin sheets and style elements with import statements into one long stylesheet... that doesn't seem like a good idea to me. This means we lose all context of how why and where a stylesheet got included. It also ignores that @import
can have its own media value, and that they can be recursive. We also shouldn't ignore @import
on cross-origin sheets.
Please be sure to include tests that covers all of these things.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: Paired on the above, refactored as discussed.
lib/core/utils/preload-cssom.js
Outdated
* @param {Boolean} isExternal flag to specify if the stylesheet was fetched via xhr | ||
* @param {String} shadowId (optional) string representing shadowId if style/ sheet was constructed from shadowDOM assets | ||
*/ | ||
function getCssomSheet(sheet, isExternal, shadowId) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be a 1-liner: const getCssomSheet = (sheet, isExternal, shadowId) => ({ sheet, isExternal, shadowId })
.
Also, this function seems unnecessary.
lib/core/utils/preload-cssom.js
Outdated
* @private | ||
*/ | ||
function getRootNode(node) { | ||
var doc = (node.getRootNode && node.getRootNode()) || document; // this is for backwards compatibility |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
lib/core/utils/preload.js
Outdated
function isValidPreloadObject(preload) { | ||
return ( | ||
typeof preload === 'object' && | ||
preload.hasOwnProperty('assets') && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can remove the .hasOwnProperty
check, as Array.isArray(undefined)
returns false
.
* @param {Object} options run configuration options (or defaults) passed via axe.run | ||
* @return {boolean} | ||
*/ | ||
axe.utils.shouldPreload = function shouldPreload(options) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could simplify this:
axe.utils.shouldPreload = options => options && isValidPreloadObject(options.preload)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why was my comment marked as resolved? You didn't change anything or respond.....?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Simply because, I do not want to check if preload
is a valid object
until I am sure that it is an object
. Hence a boolean
check and an undefined
check earlier.
Preload can take 3 values: true
, false
or { assets: ['assetKey'] }
as defined in the docs.
Doing this options && isValidPreloadObject(options.preload)
ignores and overrides the boolean values due to &&
.
Scenarios (with your suggestion):
Preload Value | options | isValidPreloadObject | output | expected |
---|---|---|---|---|
options: undefined or null |
false | false | false | false |
options: { preload : true } |
true | false | false | true (BOOM!) |
options: { preload : false } |
true | false | false | false |
options: { preload : { assets: ['cssom'] } } |
true | true | true | true |
I am sure there is a clever way to shorten things further, but this keeps it easy to follow.
Prefer to keep this for that reason.
Appreciate the comments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Discussed on Slack. Disregard my comment. This is good as-is!
lib/core/utils/preload.js
Outdated
|
||
if (!areRequestedAssetsValid) { | ||
throw new Error( | ||
`Requested assets, not supported by aXe.` + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIRC we're not capitalizing the "x" anymore.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
to avoid i18n
concerns removed aXe.
lib/core/utils/preload-cssom.js
Outdated
function getAllRootsInTree(tree) { | ||
/** | ||
* Clone of axe.commons.dom.getRootNode | ||
* Note: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like something that needs to be fixed first. Duplicating these functions will lead to maintenance nightmares.
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
} | ||
return doc; | ||
}; | ||
dom.getRootNode = axe.utils.getRootNode; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we add a warning here when this method is called? Just aliasing it doesn't seem to give users any reason not to use it.
Something like:
let didWarn = false
dom.getRootNode = (...args) => {
if (!didWarn) {
console.warn('axe-core: dom.getRootNode has been deprecated; use utils.getRootNode instead.')
didWarn = true
}
axe.utils.getRootNode(...args)
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this.
Reason I did not do this is right away is, we are bypassing the function to axe.utils
for the time being.
The jsdoc
@deprecated
notice is a flag for the dev's (us) to take some action on a later date.
There are multiple places with in the code base still referring to axe.commons.dom.getRootNode
, until we change all these references internally first, to axe.utils.getRootNode
believe we should not warn on the console.
And before all the internal changes come be made, this PR needs to be merged.
Also reckon a deprecation notice should dictate when an API is going to be deprecated, so we need to agree on that, something like this function has been deprecated from v3.2.0, use newFunction
.
Happy to add this, once we reach that consensus.
@marcysutton @WilcoFiers - thoughts?
Thanks @stephenmathieson
|
||
// concat all cssText into a string for inline rules | ||
const inlineRulesCssText = inlineRules | ||
.reduce((out, rule) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This .reduce()
seems to be performing the function of .map()
. Should we refactor to use the intended native method here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am taking each CSSRule
and appending its cssText
in to an array - out
. I intend to keep this as reduce
, as is, because I believe there will be logic going into this iteration as I tackle for @import
and media
rules.
Appreciate the comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
cssom
.preload
is built, this has not been injected into currentrun
methods orchecks
.audit.run
calls aaxe.utils.preload
queue to retrieve any external assets, and parses the results and passes it back toaudit.run
.Closes issue:
Reviewer checks
Required fields, to be filled out by PR reviewer(s)